import supertest from 'supertest';
import io from 'socket.io-client';
import rtmIncomingEvents from '../app/rtm/incoming/eventTypes';
import rtmOutgoingEvents from '../app/rtm/outgoing/eventTypes';
import concreteApp from '../app';

import componentContainer from '../components/componentContainer';

const request = supertest.agent(concreteApp.listen(10020));

componentContainer.init(concreteApp);

const redis = componentContainer.getRedis();
const app = {};
const SOCKET_URL = 'http://localhost:10020';

export function co(client, event, flag = true) {
  if (!flag) {
    return () =>
      new Promise((resolve, reject) => {
        const t = setTimeout(resolve, 500);
        client.on(event, () => {
          clearTimeout(t);
          reject();
        });
      });
  }

  return fn =>
    new Promise(resolve =>
      client.on(event, message => (fn ? resolve(fn(message)) : resolve(message))));
}

export function login(client) {
  return () => client.emit(rtmIncomingEvents.LOGIN, { token: client.__query.token });
}

export function doLogin(client) {
  return async () => {
    await client.receiveHello();
    client.sendLoginMessage();
    return client.receiveLogin();
  };
}

app.connectedSocket = [];

app.disconnectAll = async function disconnectAll() {
  await Promise.all(app.connectedSocket.map(c =>
    new Promise((resolve) => {
      if (c.disconnected) resolve();
      c.on('disconnect', resolve);
      c.disconnect();
    })));
  app.connectedSocket = [];
};

app.getSocketClient = function getSocketClient(query) {
  return function realCall() {
    const client = io.connect(SOCKET_URL, { query });
    client.__query = query;
    const stubMethods = [
      ['Hello', rtmOutgoingEvents.HELLO],
      ['Login', rtmOutgoingEvents.LOGGED_IN],
      ['UserLogin', rtmOutgoingEvents.USER_LOGIN],
      ['UserLogout', rtmOutgoingEvents.USER_LOGOUT],
      ['Message', rtmIncomingEvents.MESSAGE],
      ['ChannelCreated', rtmOutgoingEvents.CHANNEL_CREATED],
      ['ChannelJoined', rtmOutgoingEvents.CHANNEL_JOINED],
      ['ChannelLeft', rtmOutgoingEvents.CHANNEL_LEFT],
      ['SkynetLogin', rtmOutgoingEvents.SKYNET_LOGIN],
      ['SkynetLogout', rtmOutgoingEvents.SKYNET_LOGOUT],
      ['JanusLogin', rtmOutgoingEvents.JANUS_LOGIN],
      ['JanusLogout', rtmOutgoingEvents.JANUS_LOGOUT],
      ['AddWait', rtmOutgoingEvents.ADD_WAIT],
      ['RemoveWait', rtmOutgoingEvents.REMOVE_WAIT],
      ['UrgentTeacherReplacement', rtmOutgoingEvents.URGENT_TEACHER_REPLACEMENT],
    ];

    stubMethods.forEach(([name, event]) => {
      client[`receive${name}`] = co(client, event);
      client[`dontReceive${name}`] = co(client, event, false);
    });

    client.sendLoginMessage = login(client);
    client.doLogin = doLogin(client);
    client.__token = query.token;
    client.sendMessage = message => client.emit(rtmIncomingEvents.MESSAGE, message);
    client.on(rtmOutgoingEvents.LOGGED_IN, (data) => {
      client.__id = data._id;
      client.__channels = data.channels;
    });
    app.connectedSocket.push(client);
    return client;
  };
};

app.disconnect = function disconnect(...clients) {
  clients.forEach(c => c.disconnect());
};

app.publishRedisEvent = async function publishRedisEvent(event, payload) {
  await redis.publish(event, JSON.stringify(payload));
};

app.getRedis = function getRedis() {
  return redis;
};

['Peter', 'Josh', 'Erik'].forEach((name) => {
  app[`get${name}Socket`] = app.getSocketClient({ role: 'admin', token: name });
});

['Ruby', 'Lucy', 'Amy'].forEach((name) => {
  app[`get${name}Socket`] = app.getSocketClient({ role: 'teacher', token: name });
});

['StudentService', 'StudentServiceUsr1', 'TeacherService', 'TeacherServiceUsr1'].forEach((name) => {
  app[`get${name}Socket`] = app.getSocketClient({ role: 'admin', token: name });
});

function patchRequest(_request) {
  // eslint-disable-next-line
  _request.apiPost = url =>
    _request
      .post(url)
      .set('Content-Type', 'application/json')
      .set('Accept', 'application/json');
}

patchRequest(request);

function getModel(modelName) {
  return componentContainer.getMongooseClient().model(modelName);
}

function randomString(length) {
  let text = '';
  const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  // eslint-disable-next-line
  for (let i = 0; i < length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }

  return text;
}

async function prepareJoinChannel(channelId, meToken) {
  return request.apiPost('/api/channels/join').send({
    channel: channelId,
    token: meToken,
  });
}

async function prepareChannel(ownerToken, members = [], isPrivate) {
  const { body: { channel } } = await request.apiPost('/api/channels/create').send({
    name: `channel-${randomString(6)}`,
    token: ownerToken,
    isPrivate,
  });

  for (const member of members) {
    await prepareJoinChannel(channel._id, member); // eslint-disable-line
  }

  return channel;
}

async function delay(timeout = 500) {
  return new Promise(resolve => setTimeout(resolve, timeout));
}

export default app;
export {
  app,
  rtmIncomingEvents,
  rtmOutgoingEvents,
  request,
  prepareChannel,
  prepareJoinChannel,
  getModel,
  delay,
};
